Criterion B Details

# Default ecosystem code for template development.
# This line is replaced by build_ecosystem_pages.py for each ecosystem.
ecosystem_code = 'Laguna Tectonica'

Import Python modules.

import os
import yaml
from pathlib import Path
from lonboard import Map
from rle.core import Ecosystems, criterion_b_status, rle_category
from rle.core.eoo import make_eoo
from rle.core.aoo import make_aoo_grid

Load the country config file.

project_root = os.environ.get('PIXI_PROJECT_ROOT', str(Path('..').resolve()))
config_path = Path(project_root) / 'config' / 'country_config.yaml'
with open(config_path) as f:
    config = yaml.safe_load(f)

# Ecosystem index (the COG pixel value for this ecosystem), looked up by code
# from the canonical index table. Absent if the index has not been built yet.
import csv
ecosystem_index = None
index_csv = Path(project_root) / 'config' / 'ecosystems' / 'index.csv'
if index_csv.exists():
    with open(index_csv) as f:
        for row in csv.DictReader(f):
            if row['code'] == ecosystem_code:
                ecosystem_index = int(row['index'])
                break

Load & Filter Ecosystem Data

Load data for all the ecosystems.

import sys
# Shared config helpers live in scripts/; make them importable from here.
sys.path.insert(0, str(Path(project_root) / 'scripts'))
from _config import ensure_vector_source

source = config['ecosystem_source']
# ecosystem_code_column is optional: fall back to the name column.
ecosystem_column = source.get('ecosystem_code_column') or source.get('ecosystem_name_column')
# Prefer the ecosystem-sorted `optimized_data` copy when configured, so that
# filtering to one ecosystem uses parquet predicate pushdown instead of loading
# the whole national map into memory. Falls back to `data`.
ecosystems = Ecosystems.from_file(
    ensure_vector_source(source.get('optimized_data') or source['data']),
    ecosystem_column=ecosystem_column,
    ecosystem_name_column=source.get('ecosystem_name_column'),
    functional_group_column=source.get('functional_group_column')
)

Filter by the Laguna Tectonica and check the number of features.

ecosystem = ecosystems.filter(ecosystem_code)
has_data = ecosystem.size() > 0
print(f'{ecosystem.size() = }')
if not has_data:
    from IPython.display import Markdown, display
    display(Markdown(
        f'**No spatial data found for {ecosystem_code}.** '
        f'Criterion B calculations are skipped.'
    ))
ecosystem.size() = 22

Extent of Occurrence (EOO) (subcriterion B1)

Extent of occurrence (EOO). The EOO of an ecosystem is the area (km2) of a minimum convex polygon – the smallest polygon in which no internal angle exceeds 180° that encompasses all known current spatial occurrences of the ecosystem type.

The minimum convex polygon (also known as a convex hull) must not exclude any areas, discontinuities or disjunctions, regardless of whether the ecosystem can occur in those areas or not. Regions such as oceans (for terrestrial ecosystems), land (for coastal or marine ecosystems), or areas outside the study area (such as in a different country) must remain included within the minimum convex polygon to ensure that this standardised method is comparable across ecosystem types. In addition, these features contribute to spreading risks across the distribution of the ecosystem by making different parts of its distribution more spatially independent.

Calculate EOO

Start by calculating the convex hull of the ecosystem’s distribution.

import geopandas as gpd

if has_data:
    ecosystem_geometry = ecosystem.geometry.union_all()
    gdf_ecosystem_polygons = gpd.GeoDataFrame(geometry=[ecosystem_geometry], crs=ecosystem.geometry.crs)
    hull = ecosystem_geometry.convex_hull
    gdf_hull = gpd.GeoDataFrame(geometry=[hull], crs=ecosystem.geometry.crs)

Display the ecosystem’s distribution and the convex hull.

from lonboard import Map, PolygonLayer
from rle.core.viz import smart_map

if has_data:
    eoo_hull = make_eoo(ecosystem).compute()
    display(smart_map([eoo_hull, ecosystem]))
/home/runner/work/rle-tyler-colombia/rle-tyler-colombia/.pixi/envs/default/lib/python3.11/site-packages/lonboard/_geoarrow/ops/reproject.py:116: UserWarning: Input being reprojected to EPSG:4326 CRS.
Lonboard is only able to render data in EPSG:4326 projection.
  warnings.warn(
/home/runner/work/rle-tyler-colombia/rle-tyler-colombia/.pixi/envs/default/lib/python3.11/site-packages/lonboard/_geoarrow/ops/reproject.py:116: UserWarning: Input being reprojected to EPSG:4326 CRS.
Lonboard is only able to render data in EPSG:4326 projection.
  warnings.warn(
if has_data:
    hull_ea = gdf_hull.to_crs("ESRI:54034")
    eoo = hull_ea.geometry.iloc[0].area / 1e6
    print(f'EOO is {eoo:.1f} km2')
EOO is 91019.2 km2

Then calculate the area of the convex hull polygon.

Direct calculation of EOO

EOO can also be calculated directly using …

if has_data:
    ecosystem.eoo

Verify that the area returned by calling make_eoo(ecosystem).compute().area_km2 is the same as the area of the convex hull polygon.

if has_data:
    assert ecosystem.eoo == eoo

Area of Occupancy (AOO) (subcriterion B2)

The protocol for this adjustment includes the following steps:

  1. Intersect AOO grid with the ecosystem’s distribution map.
  2. Calculate extent of the ecosystem type in each grid cell (area) and sum these areas to obtain the total ecosystem area (total area).
  3. Arrange grid cells in ascending order based on their area (smaller first). Calculate accumulated sum of area per cell (cumulative area).
  4. Calculate cumulative proportion by dividing cumulative area by total area (cumulative proportion takes values between 0 and 1)
  5. Calculate AOO by counting the number of cells with a cumulative proportion greater than 0.01 (i.e. exclude cells that in combination account for up to 1% of the total mapped extent of the ecosystem type).

AOO Calculation Details

Intersect AOO grid and ecosystem map

  1. Intersect AOO grid with the ecosystem’s distribution map
from pathlib import Path
from rle.core.aoo import make_aoo_grid_cached

if has_data:
    # Prefer a prebuilt grid cache (e.g. a gs:// URI) so the national AOO grid
    # is not recomputed during CI renders — computing it from the full national
    # ecosystem map peaks at many GB of RAM. Fall back to a local cache when no
    # prebuilt cache is configured. Build one with `pixi run build-caches`.
    cache_path = (
        source.get('aoo_grid_cache_url')
        or (Path(project_root) / '.cache' / 'aoo_grid.parquet')
    )
    aoo_grid = make_aoo_grid_cached(ecosystems, cache_path=cache_path)
    aoo_grid_filtered = aoo_grid.filter_by_ecosystem(ecosystem_code)

Visualize variations in the AOO grid.

from matplotlib.colors import LinearSegmentedColormap
from lonboard.colormap import apply_continuous_cmap
from rle.core.aoo import slugify_ecosystem_name

ecosystem_column = slugify_ecosystem_name(ecosystem_code)
if has_data:
    cmap = LinearSegmentedColormap.from_list("white_red", ["white", "red"])
    values = aoo_grid_filtered.grid_cells[ecosystem_column].values
    normalized = (values - values.min()) / (values.max() - values.min())
    colors = apply_continuous_cmap(normalized, cmap)
    display(smart_map([(aoo_grid_filtered, {"get_fill_color": colors}), ecosystem]))
/home/runner/work/rle-tyler-colombia/rle-tyler-colombia/.pixi/envs/default/lib/python3.11/site-packages/lonboard/_geoarrow/ops/reproject.py:116: UserWarning: Input being reprojected to EPSG:4326 CRS.
Lonboard is only able to render data in EPSG:4326 projection.
  warnings.warn(

Calculate grid cell area and total area

  1. Calculate extent of the ecosystem type in each grid cell (area) and sum these areas to obtain the total ecosystem area (total area).
if has_data:
    keep = ['geometry', 'grid_col', 'grid_row', ecosystem_column]
    gdf = aoo_grid_filtered.grid_cells[keep]
    display(gdf)
geometry grid_col grid_row Laguna_Tectonica
0 POLYGON ((-77.16528 0.99486, -77.16528 1.08531... -860 11 0.046567
1 POLYGON ((-77.16528 1.08531, -77.16528 1.17576... -860 12 0.013276
2 POLYGON ((-77.07545 0.99486, -77.07545 1.08531... -859 11 0.129713
3 POLYGON ((-77.07545 1.08531, -77.07545 1.17576... -859 12 0.241891
4 POLYGON ((-75.54832 4.98013, -75.54832 5.07091... -842 55 0.002874
5 POLYGON ((-74.20084 4.70788, -74.20084 4.79862... -827 52 0.005083
6 POLYGON ((-74.11101 4.79862, -74.11101 4.88937... -826 53 0.006178
7 POLYGON ((-73.93135 4.61715, -73.93135 4.70788... -824 51 0.013624
8 POLYGON ((-73.93135 4.70788, -73.93135 4.79862... -824 52 0.012706
9 POLYGON ((-73.93135 5.07091, -73.93135 5.1617,... -824 56 0.049776
10 POLYGON ((-73.93135 5.1617, -73.93135 5.2525, ... -824 57 0.036910
11 POLYGON ((-73.84152 4.88937, -73.84152 4.98013... -823 54 0.069430
12 POLYGON ((-73.84152 5.1617, -73.84152 5.2525, ... -823 57 0.002626
13 POLYGON ((-73.84152 5.2525, -73.84152 5.34332,... -823 58 0.009160
14 POLYGON ((-73.75168 4.88937, -73.75168 4.98013... -822 54 0.066568
15 POLYGON ((-73.75168 4.98013, -73.75168 5.07091... -822 55 0.088427
16 POLYGON ((-73.75168 5.1617, -73.75168 5.2525, ... -822 57 0.026782
17 POLYGON ((-73.75168 5.43414, -73.75168 5.52498... -822 60 0.016956
18 POLYGON ((-73.66185 5.43414, -73.66185 5.52498... -821 60 0.069146
19 POLYGON ((-73.57202 5.34332, -73.57202 5.43414... -820 59 0.000045
20 POLYGON ((-73.48219 5.34332, -73.48219 5.43414... -819 59 0.011419
21 POLYGON ((-73.48219 5.43414, -73.48219 5.52498... -819 60 0.011162
22 POLYGON ((-73.2127 5.61584, -73.2127 5.70671, ... -816 62 0.010351
23 POLYGON ((-73.2127 5.88849, -73.2127 5.9794, -... -816 65 0.003728
24 POLYGON ((-73.03303 6.79829, -73.03303 6.88936... -814 75 0.001425
25 POLYGON ((-72.9432 5.43414, -72.9432 5.52498, ... -813 60 0.081713
26 POLYGON ((-72.9432 5.52498, -72.9432 5.61584, ... -813 61 0.026764
27 POLYGON ((-72.9432 6.79829, -72.9432 6.88936, ... -813 75 0.001119
28 POLYGON ((-72.85337 5.43414, -72.85337 5.52498... -812 60 0.095186
29 POLYGON ((-72.85337 5.52498, -72.85337 5.61584... -812 61 0.350842
30 POLYGON ((-72.85337 6.6162, -72.85337 6.70724,... -812 73 0.000112
31 POLYGON ((-72.85337 6.70724, -72.85337 6.79829... -812 74 0.002558
32 POLYGON ((-72.31438 5.9794, -72.31438 6.07032,... -806 66 0.005352

The column Laguna_Tectonica contains the (fractional) area of the ecosystem in each grid cell.

Sum up the areas of each grid cell to get the total area.

if has_data:
    total_area = gdf[ecosystem_column].sum()
    display(total_area)
np.float64(1.5094722890650065)

Calculate cumulative area

  1. Arrange grid cells in ascending order based on their area (smaller first). Calculate accumulated sum of area per cell (cumulative area).
if has_data:
    gdf = gdf.sort_values(by=ecosystem_column)
    gdf["cumulative_area"] = gdf[ecosystem_column].cumsum()
    display(gdf)
geometry grid_col grid_row Laguna_Tectonica cumulative_area
19 POLYGON ((-73.57202 5.34332, -73.57202 5.43414... -820 59 0.000045 0.000045
30 POLYGON ((-72.85337 6.6162, -72.85337 6.70724,... -812 73 0.000112 0.000157
27 POLYGON ((-72.9432 6.79829, -72.9432 6.88936, ... -813 75 0.001119 0.001276
24 POLYGON ((-73.03303 6.79829, -73.03303 6.88936... -814 75 0.001425 0.002701
31 POLYGON ((-72.85337 6.70724, -72.85337 6.79829... -812 74 0.002558 0.005259
12 POLYGON ((-73.84152 5.1617, -73.84152 5.2525, ... -823 57 0.002626 0.007884
4 POLYGON ((-75.54832 4.98013, -75.54832 5.07091... -842 55 0.002874 0.010759
23 POLYGON ((-73.2127 5.88849, -73.2127 5.9794, -... -816 65 0.003728 0.014487
5 POLYGON ((-74.20084 4.70788, -74.20084 4.79862... -827 52 0.005083 0.019570
32 POLYGON ((-72.31438 5.9794, -72.31438 6.07032,... -806 66 0.005352 0.024923
6 POLYGON ((-74.11101 4.79862, -74.11101 4.88937... -826 53 0.006178 0.031101
13 POLYGON ((-73.84152 5.2525, -73.84152 5.34332,... -823 58 0.009160 0.040261
22 POLYGON ((-73.2127 5.61584, -73.2127 5.70671, ... -816 62 0.010351 0.050612
21 POLYGON ((-73.48219 5.43414, -73.48219 5.52498... -819 60 0.011162 0.061774
20 POLYGON ((-73.48219 5.34332, -73.48219 5.43414... -819 59 0.011419 0.073193
8 POLYGON ((-73.93135 4.70788, -73.93135 4.79862... -824 52 0.012706 0.085900
1 POLYGON ((-77.16528 1.08531, -77.16528 1.17576... -860 12 0.013276 0.099176
7 POLYGON ((-73.93135 4.61715, -73.93135 4.70788... -824 51 0.013624 0.112799
17 POLYGON ((-73.75168 5.43414, -73.75168 5.52498... -822 60 0.016956 0.129755
26 POLYGON ((-72.9432 5.52498, -72.9432 5.61584, ... -813 61 0.026764 0.156520
16 POLYGON ((-73.75168 5.1617, -73.75168 5.2525, ... -822 57 0.026782 0.183301
10 POLYGON ((-73.93135 5.1617, -73.93135 5.2525, ... -824 57 0.036910 0.220212
0 POLYGON ((-77.16528 0.99486, -77.16528 1.08531... -860 11 0.046567 0.266779
9 POLYGON ((-73.93135 5.07091, -73.93135 5.1617,... -824 56 0.049776 0.316555
14 POLYGON ((-73.75168 4.88937, -73.75168 4.98013... -822 54 0.066568 0.383123
18 POLYGON ((-73.66185 5.43414, -73.66185 5.52498... -821 60 0.069146 0.452270
11 POLYGON ((-73.84152 4.88937, -73.84152 4.98013... -823 54 0.069430 0.521700
25 POLYGON ((-72.9432 5.43414, -72.9432 5.52498, ... -813 60 0.081713 0.603413
15 POLYGON ((-73.75168 4.98013, -73.75168 5.07091... -822 55 0.088427 0.691840
28 POLYGON ((-72.85337 5.43414, -72.85337 5.52498... -812 60 0.095186 0.787026
2 POLYGON ((-77.07545 0.99486, -77.07545 1.08531... -859 11 0.129713 0.916739
3 POLYGON ((-77.07545 1.08531, -77.07545 1.17576... -859 12 0.241891 1.158630
29 POLYGON ((-72.85337 5.52498, -72.85337 5.61584... -812 61 0.350842 1.509472

Calculate cumulative proportion

  1. Calculate cumulative proportion by dividing cumulative area by total area (cumulative proportion takes values between 0 and 1)
if has_data:
    gdf["cumulative_proportion"] = gdf["cumulative_area"] / total_area
    display(gdf)
geometry grid_col grid_row Laguna_Tectonica cumulative_area cumulative_proportion
19 POLYGON ((-73.57202 5.34332, -73.57202 5.43414... -820 59 0.000045 0.000045 0.000030
30 POLYGON ((-72.85337 6.6162, -72.85337 6.70724,... -812 73 0.000112 0.000157 0.000104
27 POLYGON ((-72.9432 6.79829, -72.9432 6.88936, ... -813 75 0.001119 0.001276 0.000845
24 POLYGON ((-73.03303 6.79829, -73.03303 6.88936... -814 75 0.001425 0.002701 0.001789
31 POLYGON ((-72.85337 6.70724, -72.85337 6.79829... -812 74 0.002558 0.005259 0.003484
12 POLYGON ((-73.84152 5.1617, -73.84152 5.2525, ... -823 57 0.002626 0.007884 0.005223
4 POLYGON ((-75.54832 4.98013, -75.54832 5.07091... -842 55 0.002874 0.010759 0.007128
23 POLYGON ((-73.2127 5.88849, -73.2127 5.9794, -... -816 65 0.003728 0.014487 0.009598
5 POLYGON ((-74.20084 4.70788, -74.20084 4.79862... -827 52 0.005083 0.019570 0.012965
32 POLYGON ((-72.31438 5.9794, -72.31438 6.07032,... -806 66 0.005352 0.024923 0.016511
6 POLYGON ((-74.11101 4.79862, -74.11101 4.88937... -826 53 0.006178 0.031101 0.020604
13 POLYGON ((-73.84152 5.2525, -73.84152 5.34332,... -823 58 0.009160 0.040261 0.026672
22 POLYGON ((-73.2127 5.61584, -73.2127 5.70671, ... -816 62 0.010351 0.050612 0.033530
21 POLYGON ((-73.48219 5.43414, -73.48219 5.52498... -819 60 0.011162 0.061774 0.040924
20 POLYGON ((-73.48219 5.34332, -73.48219 5.43414... -819 59 0.011419 0.073193 0.048489
8 POLYGON ((-73.93135 4.70788, -73.93135 4.79862... -824 52 0.012706 0.085900 0.056907
1 POLYGON ((-77.16528 1.08531, -77.16528 1.17576... -860 12 0.013276 0.099176 0.065702
7 POLYGON ((-73.93135 4.61715, -73.93135 4.70788... -824 51 0.013624 0.112799 0.074728
17 POLYGON ((-73.75168 5.43414, -73.75168 5.52498... -822 60 0.016956 0.129755 0.085961
26 POLYGON ((-72.9432 5.52498, -72.9432 5.61584, ... -813 61 0.026764 0.156520 0.103692
16 POLYGON ((-73.75168 5.1617, -73.75168 5.2525, ... -822 57 0.026782 0.183301 0.121434
10 POLYGON ((-73.93135 5.1617, -73.93135 5.2525, ... -824 57 0.036910 0.220212 0.145887
0 POLYGON ((-77.16528 0.99486, -77.16528 1.08531... -860 11 0.046567 0.266779 0.176736
9 POLYGON ((-73.93135 5.07091, -73.93135 5.1617,... -824 56 0.049776 0.316555 0.209713
14 POLYGON ((-73.75168 4.88937, -73.75168 4.98013... -822 54 0.066568 0.383123 0.253813
18 POLYGON ((-73.66185 5.43414, -73.66185 5.52498... -821 60 0.069146 0.452270 0.299621
11 POLYGON ((-73.84152 4.88937, -73.84152 4.98013... -823 54 0.069430 0.521700 0.345617
25 POLYGON ((-72.9432 5.43414, -72.9432 5.52498, ... -813 60 0.081713 0.603413 0.399751
15 POLYGON ((-73.75168 4.98013, -73.75168 5.07091... -822 55 0.088427 0.691840 0.458332
28 POLYGON ((-72.85337 5.43414, -72.85337 5.52498... -812 60 0.095186 0.787026 0.521391
2 POLYGON ((-77.07545 0.99486, -77.07545 1.08531... -859 11 0.129713 0.916739 0.607324
3 POLYGON ((-77.07545 1.08531, -77.07545 1.17576... -859 12 0.241891 1.158630 0.767573
29 POLYGON ((-72.85337 5.52498, -72.85337 5.61584... -812 61 0.350842 1.509472 1.000000

Count AOO cells

  1. Calculate AOO by counting the number of cells with a cumulative proportion greater than 0.01 (i.e. exclude cells that in combination account for up to 1% of the total mapped extent of the ecosystem type).
if has_data:
    aoo = len(gdf[gdf["cumulative_proportion"] > 0.01])
    print(f'AOO is {aoo} cells')
AOO is 25 cells

AOO Calculation (direct call)

if has_data:
    aoo_count = ecosystem.aoo
    print(f'AOO: {aoo_count} grid cells')
AOO: 25 grid cells

Criterion B Summary

Criterion B status (spatial)
Vulnerable (VU) — Laguna Tectonica (Laguna Tectonica), index 60

Status reflects the spatial thresholds for EOO (B1) and AOO (B2) only. A final listing under B1/B2 additionally requires at least one of: (a) an observed or inferred continuing decline; (b) threatening processes likely to cause continuing decline within 20 years; or (c) few threat-defined locations — none of which are derived from the spatial metrics.

Sub-criterion Metric Value Category
B1 EOO 91019 km² Least Concern (LC)
B2 AOO 25 cells Vulnerable (VU)
Overall B — — Vulnerable (VU)